// dOOdad - Object-oriented programming framework for Javascript
// File: Widgets.js - Widgets base types
// Project home: https://sourceforge.net/projects/doodad-js/
// Author: Claude Petit, Quebec city
// Copyright @ 2015

if (!window.Doodad) {
	Doodad = {
		parent: window,
	};
};

if (!Doodad.Widgets) {
	Doodad.Widgets = {
		parent: Doodad,
	};
};

Doodad.Widgets.init = function() {
	"use strict";

	var widgets = this;
	
	if (widgets.initialized) {
		return;
	};

	var doodad = widgets.parent;

	if (!doodad || !doodad.init) {
		throw "'Doodad.js' is not loaded.";
	};
	if (!doodad.initialized) {
		doodad.init();
	};
		
	if (!widgets.MixIns) {
		widgets.MixIns = {
			parent: widgets,
		};
	};

	var mixIns = doodad.MixIns,
		uiMixIns = widgets.MixIns,
		tools = doodad.Tools,
		extenders = doodad.Extenders,
		exceptions = doodad.Exceptions;
	
	//==================================
	// Mix-ins
	//==================================
	
	uiMixIns.Attributes = doodad.REGISTER(doodad.MIX_IN(doodad.Class.$extend(
	{
		$TYPE_NAME: 'Doodad.Widgets.MixIns.Attributes',
		
		__attributes: doodad.ATTRIBUTE({
			main: null,
		}, extenders.ExtendObject, {maxDepth: 1, isPreserved: true}),

		validateCssClassNames: doodad.PROTECTED(function validateCssClassNames(/*optional*/cssClassNames) {
			if (tools.isNothing(cssClassNames)) {
				cssClassNames = ['main'];
			} else if (tools.isString(cssClassNames)) {
				cssClassNames = cssClassNames.split(' ');
			};
			
			if (doodad.ASSERT) {
				doodad.ASSERT(tools.isArray(cssClassNames), "Invalid css class names.");
				doodad.ASSERT(tools.every(cssClassNames, function(name) {
					return tools.hasKey(this.__attributes, name)
				}, this), "Invalid css class names.");
			};
			
			return cssClassNames;
		}),
		
		getAttributes: doodad.PUBLIC(function getAttributes(/*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			var attributes, 
				cssClassName;

			if (cssClassNames.length === 1) {
				cssClassName = cssClassNames[0];
				attributes = this.__attributes[cssClassName];
				if (!attributes) {
					this.__attributes[cssClassName] = attributes = {};
				};
			} else {
				attributes = {};
				var classes = [];
				for (var i = 0; i < cssClassNames.length; i++) {
					cssClassName = cssClassNames[i];
					var attrs = this.__attributes[cssClassName];
					if (attrs) {
						if (attrs.class) {
							tools.append(classes, attrs.class.split(' '));
						};
						attributes = tools.extend(attributes, attrs);
					};
				};
				if (classes.length) {
					attributes.class = tools.unique(classes).join(' ');
				};
			};
			
			return attributes;
		}),
		setAttributes: doodad.PUBLIC(function setAttributes(attributes, /*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			doodad.ASSERT && doodad.ASSERT(doodad.isJsObject(attributes), "Invalid attributes object.");
			
			var cssClassName;

			if (attributes.class) {
				var classes = attributes.class.split(' ');
				classes = tools.unique(classes);
				for (var i = classes.length - 1; i >= 0; i--) {
					var name = classes[i];
					if (name) {
						classes[i] = name.toLowerCase();
					} else {
						classes.splice(i, 1);
					};
				};
				attributes.class = classes.join(' ');
			};
			for (var i = 0; i < cssClassNames.length; i++) {
				cssClassName = cssClassNames[i];
				this.__attributes[cssClassName] = attributes;
			};
		}),
		renderAttributes: doodad.PUBLIC(function renderAttributes(/*optional*/cssClassNames) {
			var attributes = this.getAttributes(cssClassNames);
		
			var result = '',
				keys = tools.keys(attributes),
				keysLen = keys.length;
				
			for (var i = 0; i < keysLen; i++) {
				var name = keys[i],
					val = attributes[name];
					
				if (!tools.isNothing(val)) {
					result += ' ' + tools.escapeHtml(name) + '="' + tools.escapeHtml(val) + '"';
				};
			};
			
			return result;
		}),
		applyAttributes: doodad.PUBLIC(function applyAttributes(/*optional*/cssClassNames, elements) {
			if (tools.isElement(elements)) {
				elements = [elements];
			};
		
			if (doodad.ASSERT) {
				doodad.ASSERT(tools.isArrayLike(elements), "Invalid elements array.");
				doodad.ASSERT(tools.every(elements, tools.isElement), "Invalid elements array.");
			};

			var attributes = this.getAttributes(cssClassNames),
				keys = tools.keys(attributes),
				keysLen = keys.length,
				elementsLen = elements.length,
				name,
				val;

			for (var i = 0; i < keysLen; i++) {
				name = keys[i];
				val = attributes[name];

				for (var j = 0; j < elementsLen; j++) {
					if (tools.isNothing(val)) {
						elements[j].removeAttribute(name);
					} else {
						elements[j].setAttribute(name, val);
					};
				};
			};
		}),
		linkAttributes: doodad.PUBLIC(doodad.CALL_FIRST(function linkAttributes(source, /*optional*/sourceCssClassName, /*optional*/cssClassName) {
			doodad.ASSERT && doodad.ASSERT((source !== this) && doodad._implements(source, uiMixIns.Attributes), "Invalid source object.");
			
			sourceCssClassName = source.validateCssClassNames(sourceCssClassName)[0];

			if (tools.isNothing(cssClassName)) {
				cssClassName = sourceCssClassName;
			};
			
			this.__attributes[cssClassName] = source.__attributes[sourceCssClassName];
			
			this._super(source, sourceCssClassName, cssClassName);
		})),
		clearAttributes: doodad.PUBLIC(doodad.CALL_FIRST(function clearAttributes(/*optional*/cssClassName) {
			if (tools.isNothing(cssClassName)) {
				this.restorePreserved('__attributes');
			} else {
				cssClassName = this.validateCssClassNames(cssClassName)[0];
				var preserved = this.getPreserved('__attributes');
				if (tools.hasKey(preserved, cssClassName)) {
					this.__attributes[cssClassName] = preserved[cssClassName];
				} else {
					delete this.__attributes[cssClassName];
				};
			};
			
			this._super(cssClassName);
		})),
	})));
	
	uiMixIns.Identities = doodad.REGISTER(doodad.MIX_IN(uiMixIns.Attributes.$extend(
	{
		$TYPE_NAME: 'Doodad.Widgets.MixIns.Identities',
		
		__identities: doodad.ATTRIBUTE({
			main: null,
		}, extenders.ExtendObject, {maxDepth: 1, isPreserved: true}),

		getIdentity: doodad.PUBLIC(function getIdentity(/*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			var identity, 
				cssClassNamesLen = cssClassNames.length,
				cssClassName;
			
			if (cssClassNamesLen === 1) {
				cssClassName = cssClassNames[0];
				identity = this.__identities[cssClassName];
				if (!identity) {
					this.__identities[cssClassName] = identity = {
						id: null,
						name: null,
						class: null,
					};
				};
			} else {
				identity = {
					id: null,
					name: null,
					class: null,
				};
				
				var classes = [];
				
				for (var i = 0; i < cssClassNamesLen; i++) {
					cssClassName = cssClassNames[i];
					var attrs = this.__identities[cssClassName];
					if (attrs) {
						if (!tools.isNothing(attrs.id)) {
							identity.id = attrs.id;
						};
						if (!tools.isNothing(attrs.name)) {
							identity.name = attrs.name;
						};
						if (tools.isStringAndNotEmpty(attrs.class)) {
							tools.append(classes, attrs.class.split(' '));
						};
					};
				};
				
				if (classes.length) {
					identity.class = tools.unique(classes).join(' ');
				};
			};

			return identity;
		}),
		setIdentity: doodad.PUBLIC(function setIdentity(identity, /*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			doodad.ASSERT && doodad.ASSERT(doodad.isJsObject(identity), "Invalid identity object.");
			
			var cssClassName;
			
			identity = {
				id: identity.id || null,
				name: identity.name || null,
				class: identity.class || null,
			};

			if (identity.class) {
				var classes = identity.class.split(' ');
				classes = tools.unique(classes);
				for (var i = classes.length - 1; i >= 0; i--) {
					var name = classes[i];
					if (name) {
						classes[i] = name.toLowerCase();
					} else {
						classes.splice(i, 1);
					};
				};
				identity.class = classes.join(' ');
			};

			for (var i = 0; i < cssClassNames.length; i++) {
				cssClassName = cssClassNames[i];
				this.__identities[cssClassName] = identity;
			};
		}),
		applyIdentity: doodad.PUBLIC(function applyIdentity(/*optional*/cssClassNames, elements) {
			if (tools.isElement(elements)) {
				elements = [elements];
			};
		
			if (doodad.ASSERT) {
				doodad.ASSERT(tools.isArrayLike(elements), "Invalid elements array.");
				doodad.ASSERT(tools.every(elements, tools.isElement), "Invalid elements array.");
			};

			var attributes = this.getAttributes(cssClassNames),
				elementsLen = elements.length,
				element,
				classes;

			for (var i = 0; i < elementsLen; i++) {
				element = elements[i];
			
				if (tools.isNothing(attributes.id)) {
					element.removeAttribute('id');
				} else {
					element.setAttribute('id', attributes.id);
				};
				
				if (tools.isNothing(attributes.name)) {
					element.removeAttribute('name');
				} else {
					element.setAttribute('name', attributes.name);
				};
				
				if (tools.isNothing(attributes.class)) {
					element.removeAttribute('class');
				} else {
					element.setAttribute('class', attributes.class);
				};
				
			};
		}),
		
		getAttributes: doodad.OVERRIDE(function getAttributes(/*optional*/cssClassNames) {
			var attributes = this._super(cssClassNames),
				identity = this.getIdentity(cssClassNames);

			if (identity.id) {
				attributes.id = identity.id;
			};
			
			if (identity.name) {
				attributes.name = identity.name;
			};
			
			if (identity.class) {
				if (attributes.class) { 
					// Merge classes from attributes and identity
					var classes = attributes.class.split(' ');
					tools.append(classes, identity.class.split(' '));
					classes = tools.unique(classes);
					attributes.class = classes.join(' ');
				} else {
					attributes.class = identity.class;
				};
			};
			
			return attributes;
		}),
		linkAttributes: doodad.OVERRIDE(function linkAttributes(source, /*optional*/sourceCssClassName, /*optional*/cssClassName) {
			if (doodad._implements(source, uiMixIns.Identities), "Invalid source object.") {
				var	identities = source.__identities,
					identity = identities[sourceCssClassName];
				if (!identity) {
					identities[sourceCssClassName] = identity = {
						id: null,
						name: null,
						class: null,
					};
				};
				this.__identities[cssClassName] = identity;
			};
			
			this._super(source, sourceCssClassName, cssClassName);
		}),
		clearAttributes: doodad.OVERRIDE(function clearAttributes(/*optional*/cssClassName) {
			if (tools.isNothing(cssClassName)) {
				this.restorePreserved('__identities');
			} else {
				var preserved = this.getPreserved('__identities');
				if (tools.hasKey(preserved, cssClassName)) {
					this.__identities[cssClassName] = preserved[cssClassName];
				} else {
					delete this.__identities[cssClassName];
				};
			};

			this._super(cssClassName);
		}),
	})));
	
	var __styleRegEx__ = /([a-z0-9]+)([A-Z])?/g;
	uiMixIns.Styles = doodad.REGISTER(doodad.MIX_IN(uiMixIns.Attributes.$extend(
	{
		$TYPE_NAME: 'Doodad.Widgets.MixIns.Styles',
		
		__styles: doodad.ATTRIBUTE({
			main: null,
		}, extenders.ExtendObject, {maxDepth: 1, isPreserved: true}),

		getStyles: doodad.PUBLIC(function getStyles(/*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			var styles, 
				cssClassName,
				cssClassNamesLen = cssClassNames.length;
			
			if (cssClassNames.length === 1) {
				cssClassName = cssClassNames[0];
				styles = this.__styles[cssClassName];
				if (!styles) {
					this.__styles[cssClassName] = styles = {};
				};
			} else {
				styles = {};
				for (var i = 0; i < cssClassNames.length; i++) {
					cssClassName = cssClassNames[i];
					var attrs = this.__styles[cssClassName];
					if (attrs) {
						styles = tools.extend(styles, attrs);
					};
				};
			};
			
			return styles;
		}),
		setStyles: doodad.PUBLIC(function setStyles(styles, /*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);
			
			doodad.ASSERT && doodad.ASSERT(doodad.isJsObject(styles), "Invalid styles object.");

			var cssClassName;
			
			for (var i = 0; i < cssClassNames.length; i++) {
				cssClassName = cssClassNames[i];
				this.__styles[cssClassName] = styles;
			};
		}),
		applyStyles: doodad.PUBLIC(function applyStyles(/*optional*/cssClassNames, elements) {
			if (tools.isElement(elements)) {
				elements = [elements];
			};
		
			if (doodad.ASSERT) {
				doodad.ASSERT(tools.isArrayLike(elements), "Invalid elements array.");
				doodad.ASSERT(tools.every(elements, tools.isElement), "Invalid elements array.");
			};

			var styles = this.getStyles(cssClassNames),
				keys = tools.keys(styles),
				keysLen = keys.length,
				elementsLen = elements.length,
				name,
				val;

			for (var i = 0; i < keysLen; i++) {
				name = keys[i];
				val = styles[name];
				
				if (tools.isNothing(val)) {
					val = null;
				} else if (tools.isFunction(val.toString)) {
					val = val.toString();
				};

				for (var j = 0; j < elementsLen; j++) {
					elements[j].style[name] = val;
				};
			};
		}),
		
		getAttributes: doodad.OVERRIDE(function getAttributes(/*optional*/cssClassNames) {
			cssClassNames = this.validateCssClassNames(cssClassNames);

			var attributes = this._super(cssClassNames),
				styles = this.getStyles(cssClassNames),
				result = '',
				keys = tools.keys(styles),
				keysLen = keys.length,
				name,
				val;
				
			for (var i = 0; i < keysLen; i++) {
				name = keys[i];
				val = styles[name];
					
				if (!tools.isNothing(val)) {
					__styleRegEx__.lastIndex = 0;
					
					var newName = '',
						sep = __styleRegEx__.exec(name);
						
					while (sep) {
						newName += sep[1];
						if (sep[2]) {
							newName += '-' + sep[2].toLowerCase();
						};
						sep = __styleRegEx__.exec(name);
					};
					
					if (newName.length) {
						if (tools.isFunction(val.toString)) {
							val = val.toString();
						};
						result += newName + ':' + val + ';';
					};
				};
			};
			
			if (result.length) {
				attributes.style = result;
			} else {
				delete attributes.style;
			};

			return attributes;
		}),
		linkAttributes: doodad.OVERRIDE(function linkAttributes(source, /*optional*/sourceCssClassName, /*optional*/cssClassName) {
			if (doodad._implements(source, uiMixIns.Styles), "Invalid source object.") {
				var	styles = source.__styles,
					style = styles[sourceCssClassName];
				if (!style) {
					styles[sourceCssClassName] = style = {};
				};
				this.__styles[cssClassName] = style;
			};
			
			this._super(source, sourceCssClassName, cssClassName);
		}),
		clearAttributes: doodad.OVERRIDE(function clearAttributes(/*optional*/cssClassName) {
			if (tools.isNothing(cssClassName)) {
				this.restorePreserved('__styles');
			} else {
				var preserved = this.getPreserved('__styles');
				if (tools.hasKey(preserved, cssClassName)) {
					this.__styles[cssClassName] = preserved[cssClassName];
				} else {
					delete this.__styles[cssClassName];
				};
			};
			
			this._super(cssClassName);
		}),
	})));

	uiMixIns.Render = doodad.REGISTER(doodad.MIX_IN(doodad.Class.$extend(
							mixIns.Creatable,
	{
		$TYPE_NAME: 'Doodad.Widgets.MixIns.Render',
	
		onPreRender: doodad.EVENT(true), // function onPreRender(obj, eventName, container)
		onRender: doodad.EVENT(false), // function onRender(obj, eventName, container)
		onPostRender: doodad.EVENT(false), // function onPostRender(obj, eventName)
		
		__container: null,
		__elements: null,
		
		acquire: doodad.PUBLIC(doodad.METHOD()),
		release: doodad.PUBLIC(function() {
			this.clearJsEvents();
		}),
		
		getContainer: doodad.PUBLIC(function getContainer() {
			return this.__container;
		}),
		getElements: doodad.PUBLIC(function getElements() {
			return this.__elements;
		}),
		setElements: doodad.PUBLIC(function setElements(/*optional*/elements) {
			var prevElements = this.getElements(),
				elementsLen,
				element;
			if (prevElements) {
				elementsLen = prevElements.length;
				for (var i = 0; i < elementsLen; i++) {
					delete prevElements[i].doodadObject;
				};
			};
			if (!tools.isNothing(elements)) {
				if (!tools.isArray(elements)) {
					elements = [elements];
				};
				elementsLen = elements.length;
				for (var i = 0; i < elementsLen; i++) {
					element = elements[i];
					doodad.ASSERT && doodad.ASSERT(tools.isElement(element), "Invalid element.");
					tools.defineProperty(element, 'doodadObject', {
						value: this,
						configurable: true, // allow "delete"
					});
				};
			};
			this.__elements = (elements || null);
		}),
		render: doodad.PUBLIC(doodad.MUST_OVERRIDE(doodad.CALL_FIRST(function render(/*optional*/container) {
			if (tools.isNothing(container)) {
				container = this.__container;
			};
			if (!this.onPreRender(container)) {
				this.unrender();

				this.__container = container;
				this._super(container);
				this.onRender(container);

				var elements = this.getElements();
				if (tools.isNothing(elements)) {
					doodad.ASSERT && doodad.ASSERT(tools.isElement(container), "You must call 'this.setElements' on 'this.render'.");
					elements = tools.filter(container.childNodes, tools.isElement);
					this.setElements(elements);
				};
				
				this.acquire();
				
				this.onPostRender();
			};
		}))),
		unrender: doodad.PUBLIC(doodad.MUST_OVERRIDE(doodad.CALL_FIRST(function unrender(container) {
			if (this.__container) {
				this.release();
				
				this._super();
				
				this.__container = null;
			};
			
			this.setElements(null);
		}))),
		
		destroy: doodad.OVERRIDE(function destroy() {
			this.unrender();
			this._super();
		}),
	})));

	//==================================
	// Widget base
	//==================================
	
	widgets.UISection = doodad.REGISTER(doodad.Object.$extend(
							uiMixIns.Identities,
							uiMixIns.Styles,
	{
		$TYPE_NAME: 'Doodad.Widgets.UISection',
	}));
	
	widgets.Widget = doodad.REGISTER(doodad.BASE(widgets.UISection.$extend(
							uiMixIns.Render,
	{
		$TYPE_NAME: 'Doodad.Widgets.Widget',
		
		parent: null,

		create: doodad.CALL_FIRST(doodad.OVERRIDE(function create(parent) {
			doodad.ASSERT && doodad.ASSERT(tools.isNothing(parent) || doodad._instanceof(parent, doodad.Widgets.Widget), "Parent must inherit 'Doodad.Widgets.Widget'.");
			
			this._super();
			
			this.parent = parent;
		})),
	})));

	
	
	widgets.initialized = true;
};
